package com.runa.protocol.dto;

import com.runa.protocol.enums.ByteOrder;
import com.runa.protocol.enums.COTPType;
import com.runa.protocol.enums.S7Enum;
import com.runa.protocol.util.ByteUtil;

import java.util.ArrayList;
import java.util.List;

public class S7RequestData implements IRequestData<byte[]> {

    private COTPSectionData cotpSectionData;

    private S7Enum.PDUType pduType;

    private short pduRef;

    private S7Enum.FunCode funCode;

    //以下是初始化包才有的字段
    private int maxCaller;

    private int maxCallee;

    private int pduLength;

    //参数item
    private List<S7ParamItem> paramItems = new ArrayList<>();

    //数据item
    private List<S7DataItem> dataItems = new ArrayList<>();

    public S7RequestData(COTPSectionData cotpSectionData, S7Enum.PDUType pduType) {
        this.cotpSectionData = cotpSectionData;
        this.pduType = pduType;
    }

    public COTPSectionData getCotpSectionData() {
        return cotpSectionData;
    }

    public void setCotpSectionData(COTPSectionData cotpSectionData) {
        this.cotpSectionData = cotpSectionData;
    }

    public S7Enum.PDUType getPduType() {
        return pduType;
    }

    public void setPduType(S7Enum.PDUType pduType) {
        this.pduType = pduType;
    }

    public S7Enum.FunCode getFunCode() {
        return funCode;
    }

    public void setFunCode(S7Enum.FunCode funCode) {
        this.funCode = funCode;
    }

    public int getMaxCaller() {
        return maxCaller;
    }

    public void setMaxCaller(int maxCaller) {
        this.maxCaller = maxCaller;
    }

    public int getMaxCallee() {
        return maxCallee;
    }

    public void setMaxCallee(int maxCallee) {
        this.maxCallee = maxCallee;
    }

    public int getPduLength() {
        return pduLength;
    }

    public void setPduLength(int pduLength) {
        this.pduLength = pduLength;
    }

    public List<S7ParamItem> getParamItems() {
        return paramItems;
    }

    public void setParamItems(List<S7ParamItem> paramItems) {
        this.paramItems = paramItems;
    }

    public List<S7DataItem> getDataItems() {
        return dataItems;
    }

    public void setDataItems(List<S7DataItem> dataItems) {
        this.dataItems = dataItems;
    }

    @Override
    public short getTransSign() {
        return pduRef;
    }

    @Override
    public void setTransSign(short transSign) {
        this.pduRef = transSign;
    }

    @Override
    public boolean isReadWrite() {
        return !(funCode == S7Enum.FunCode.WRITE);
    }

    @Override
    public boolean check(IResponseData data) {
        if (data instanceof S7ResponseData) {
            S7ResponseData responseData = (S7ResponseData) data;
            if (this.cotpSectionData.getCotpType() == COTPType.ConnectReq) {
                return responseData.getCotpSectionData().getCotpType() == COTPType.ConnectConfirm;
            } else if (this.cotpSectionData.getCotpType() == COTPType.DataTransport) {
                if (this.pduType == S7Enum.PDUType.JOB
                        && responseData.getPduType() == S7Enum.PDUType.ACK_DATA) {
                        return this.funCode == responseData.getFunCode()
                                && this.pduRef == responseData.getTransSign();
                }
            }
        }
        return false;
    }

    @Override
    public boolean isHaveResult() {
        return true;
    }

    @Override
    public byte[] getData() {
        switch (cotpSectionData.getCotpType()) {
            case ConnectReq:
                byte[] cotp = cotpSectionData.toBytes();
                byte[] data = new byte[4+cotp.length];
                data[0] = 3;
                data[1] = 0;
                data[2] = (byte) (data.length >> 8);
                data[3] = (byte) data.length;
                System.arraycopy(cotp, 0, data, 4, cotp.length);
                return data;
            case DataTransport:
                switch (funCode) {
                    case COMM_SET:
                        return commSet();
                    case READ:
                        return read();
                    case WRITE:
                        return write();
                }
        }
        return null;
    }

    private byte[] commSet() {
        byte[] data = new byte[25];
        data[0] = 3;
        data[1] = 0;
        data[2] = 0;
        data[3] = 25;
        data[4] = 2;
        data[5] = (byte) 0xf0;
        data[6] = (byte) 0x80;
        data[7] = 0x32;
        data[8] = pduType.getCode();
        data[14] = 8;
        data[17] = funCode.getCode();
        data[19] = (byte) (maxCaller >> 8);
        data[20] = (byte) maxCaller;
        data[21] = (byte) (maxCallee >> 8);
        data[22] = (byte) maxCallee;
        data[23] = (byte) (pduLength >> 8);
        data[24] = (byte) pduLength;
        return data;
    }

    private byte[] read() {
        int paramLen = 2 + 12 * paramItems.size();
        byte[] data = new byte[17 + paramLen];
        data[0] = 3;
        data[1] = 0;
        data[2] = 0;
        data[3] = (byte) data.length;
        data[4] = 2;
        data[5] = (byte) 0xf0;
        data[6] = (byte) 0x80;
        data[7] = 0x32;
        data[8] = pduType.getCode();
        ByteUtil.shortToByte(pduRef, data, 11, ByteOrder.AB);
        data[13] = (byte) (paramLen >> 8);
        data[14] = (byte) paramLen;
        data[17] = funCode.getCode();
        data[18] = (byte) paramItems.size();
        int index = 19;
        for (S7ParamItem item : paramItems) {
            data[index++] = item.getSpecType();
            data[index++] = 10;
            data[index++] = item.getSyntaxId();
            data[index++] = item.getVariable().getCode();
            data[index++] = (byte) (item.getOperateLen() >> 8);
            data[index++] = (byte) item.getOperateLen();
            data[index++] = (byte) (item.getDbNumber() >> 8);
            data[index++] = (byte) item.getDbNumber();
            data[index++] = item.getArea().getCode();
            int address = (item.getByteAddr() << 3) | item.getBitAddr();
            data[index++] = (byte) (address >> 16);
            data[index++] = (byte) (address >> 8);
            data[index++] = (byte) address;
        }
        return data;
    }

    private byte[] write() {
        int paramLen = 2 + 12 * paramItems.size();
        int dataLen = dataItems.stream().mapToInt(S7DataItem::getByteSize).sum();
        byte[] data = new byte[17 + paramLen + dataLen];
        data[0] = 3;
        data[1] = 0;
        data[2] = 0;
        data[3] = (byte) data.length;
        data[4] = 2;
        data[5] = (byte) 0xf0;
        data[6] = (byte) 0x80;
        data[7] = 0x32;
        data[8] = pduType.getCode();
        ByteUtil.shortToByte(pduRef, data, 11, ByteOrder.AB);
        data[13] = (byte) (paramLen >> 8);
        data[14] = (byte) paramLen;
        data[15] = (byte) (dataLen >> 8);
        data[16] = (byte) dataLen;
        data[17] = funCode.getCode();
        data[18] = (byte) paramItems.size();
        int index = 19;
        for (S7ParamItem item : paramItems) {
            data[index++] = item.getSpecType();
            data[index++] = 10;
            data[index++] = item.getSyntaxId();
            data[index++] = item.getVariable().getCode();
            data[index++] = (byte) (item.getOperateLen() >> 8);
            data[index++] = (byte) item.getOperateLen();
            data[index++] = (byte) (item.getDbNumber() >> 8);
            data[index++] = (byte) item.getDbNumber();
            data[index++] = item.getArea().getCode();
            int address = (item.getByteAddr() << 3) | item.getBitAddr();
            data[index++] = (byte) (address >> 16);
            data[index++] = (byte) (address >> 8);
            data[index++] = (byte) address;
        }
        for (S7DataItem item : dataItems) {
            data[index++] = item.getReturnCode().getCode();
            data[index++] = item.getTransportSize().getCode();
            data[index++] = (byte) (item.getBitAmount() >> 8);
            data[index++] = (byte) item.getBitAmount();
            System.arraycopy(item.getData(), 0, data, index, item.getData().length);
            index += item.getData().length;
        }
        return data;
    }
}
